package modeller.databasedesignmodel.servicedemand.transaction.sqlquery.demandFetcher;

import exceptions.NoIndexOnAttributesException;
import modeller.databasedesignmodel.Attribute;
import modeller.databasedesignmodel.DatabaseDesign;
import modeller.databasedesignmodel.query.*;
import modeller.databasedesignmodel.relation.Relation;
import modeller.databasedesignmodel.relation.index.AbstractTableIndex;
import modeller.databasedesignmodel.servicedemand.IServiceDemand;
import modeller.databasedesignmodel.servicedemand.transaction.sqlquery.SQLQueryServiceDemand;
import modeller.databasedesignmodel.transaction.SQLQuery;
import queped.NeptuneHelperFunctions;

import java.util.HashSet;

/**
 * Created by:  Jason Ye
 * Date:        23/03/2012
 * Time:        09:20
 */
public class UnclusteredTreeIndexCalculatorVisitor extends AbstractIndexedSQLQueryCalculatorVisitor {

    public UnclusteredTreeIndexCalculatorVisitor(DatabaseDesign design) {
        super(design);
    }

    @Override
    public IServiceDemand calculate(SequentialScanQuery query) {

        //assume one relation per query
        try {

            Relation relation = query.getRelation().first;
            HashSet<Attribute> attributeHashSet = query.getRelation().second;
            double R = relation.getRIndexEntryToRowSize(relation.getIndexWithAttributes(attributeHashSet));
            // num records = page size / row length
            double recordsPerPage = ((double) design.getDBPageSize()) / ((double) relation.getExpectedRowSize());
            // BD (#records per page + R)
            return new SQLQueryServiceDemand(
                    getBDForQuery(query) * (R + recordsPerPage),
                    relation
            );

        } catch (NoIndexOnAttributesException e) {
            return super.calculate(query);
        }
    }

    @Override
    public IServiceDemand calculate(EqualitySearchQuery query) {

        // D(1+logF(RB))

        //assume one relation per query, and required relation is at front of list
        double D = design.getDAverageIOTime();

        try {
            return new SQLQueryServiceDemand(
                    D * (1 + getLogFRB(query)),
                    query.getRelation().getFirst()
            );
        } catch (NoIndexOnAttributesException e) {
            return super.calculate(query);
        }
    }

    @Override
    public IServiceDemand calculate(EqualityWithRangeSearchQuery query) {

        // D(logF(RB)+#matching records)
        //assume one relation per query, and required relation is at front of list

        try {

            double logFRB = getLogFRB(query);
            double D = design.getDAverageIOTime();
            int numMatchingRows = query.getNumMatchingRows();

            return new SQLQueryServiceDemand(
                    D * (logFRB + numMatchingRows),
                    query.getRelation().getFirst()
            );

        } catch (NoIndexOnAttributesException e) {
            return super.calculate(query);
        }
    }

    @Override
    public IServiceDemand calculate(InsertQuery query) {
        // D(3+logF(RB))
        try {
            return getDTimes3LogFRB(query);
        } catch (NoIndexOnAttributesException e) {
            return super.calculate(query);
        }
    }

    @Override
    public IServiceDemand calculate(UpdateDeleteQuery query) {

        // Equality Search + 2D = D(1+logF(RB)) + 2D = D(3+logF(RB))
        try {
            return getDTimes3LogFRB(query);
        } catch (NoIndexOnAttributesException e) {
            return super.calculate(query);
        }

    }

    private double getLogFRB(SQLQuery query) throws NoIndexOnAttributesException {

        Relation relation = query.getRelation().first;
        AbstractTableIndex indexUsed = relation.getIndexWithAttributes(query.getRelation().second);

        double R = relation.getRIndexEntryToRowSize(indexUsed);
        double B = relation.getBNumDbPages();
        // calculating F resquires index and fill factor
        double F = relation.getFIndexTreeFanout(indexUsed, design.getDBFillFactor());

        return NeptuneHelperFunctions.logBaseArbitrary(F, R * B);


    }

    private SQLQueryServiceDemand getDTimes3LogFRB(SQLQuery query) throws NoIndexOnAttributesException {


        return new SQLQueryServiceDemand(
                design.getDAverageIOTime()
                        * (3 + getLogFRB(query)),
                query.getRelation().getFirst()
        );
    }


}